package cn.mong.util.modelexcel;

import java.awt.Point;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;

import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;

import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import lombok.extern.slf4j.Slf4j;

/**
 * excel导出工具类
 * @author mengxianming@2021-8-19 17:29:38
 *
 */
@Slf4j
public class ModelExcelExporter {
    private ExcelWriter excelWriter = ExcelUtil.getWriter(true);
    
    private CellWriter cellWriter = new DefaultCellWriter(excelWriter); 
    
    private XSSFCellStyle headerCellStyle;
    
    public ModelExcelExporter() {
        createHeaderStyle();
    }
    
    private void createHeaderStyle() {
        XSSFCellStyle cellStyle = cellWriter.createBasicStyle();
        cellStyle.getFont().setBold(true);

        cellStyle.setShrinkToFit(true);

        this.headerCellStyle = cellStyle;
    }

    /**
     * 从指定行，列开始写入模型数据到excel。返回写入完成后excel当前的行数(y)，列数(x)
     * @param <D>
     * @param row 写开始行。从0开始
     * @param col 写开始列。从0开始
     * @param modelWriter 模型写入器
     * @param modelList 模型列表
     * @param writeHeader 是否写入头部
     * @param horizontal false-按行输出模型，true-按列输出模型
     * @return
     */
    public <D> Point writeModels(int row, int col, ModelWriter<D> modelWriter,
            Iterable<D> modelList, boolean writeHeader) {
        Point result = new Point(col, row);
        
        modelWriter.setBaseCol(col);
        modelWriter.setBaseRow(row);
        Point colRow = modelWriter.writeModels(modelList, writeHeader);
        result.x += colRow.x;
        result.y += colRow.y;
        return result;               
    }
    
    /**
     * 写入单元格列表。返回写入完成后excel当前的行数(y)，列数(x)
     * @param row
     * @param col
     * @param cells
     * @return
     */
    public Point writeCells(int row, int col, Iterable<PositionCell> cells) {
        Point maxXy = new Point(0, 0);
        cells.forEach(cell -> {
            maxXy.x = Math.max(maxXy.x, cell.getCol() + cell.getXspan());
            maxXy.y = Math.max(maxXy.y, cell.getRow() + cell.getYspan());
            getCellWriter().writeCellValue(row + cell.getRow(), col + cell.getCol(),
                    cell.getVal(), new Point(cell.getXspan(), cell.getYspan()), cell.getCellStyleCustomizer(), false);
        });
        
        maxXy.x += col;
        maxXy.y += row;
        return maxXy;        
    }
    
    
    
    /**
     * 创建注解驱动的模型写入器的实例
     * @param <D>
     * @param clazz
     * @return
     */
    protected <D> HeaderAnnotationModelWriter<D> createAnnotationModelWriter(Class<D> modelClazz){
       return new HeaderAnnotationModelWriter<D>(modelClazz, this.cellWriter, null);
    }
    
    
    /**
     * 导出当前excel内容到浏览器端。
     * @param fileName
     * @param response
     * @return
     */
    public void flushToHttpResponse(String fileName, HttpServletResponse response) {
        // 导出到excel文件流
    	final String contentType = ObjectUtil.defaultIfNull(FileUtil.getMimeType(fileName), "application/octet-stream");
    	final String charset = ObjectUtil.defaultIfNull(response.getCharacterEncoding(), CharsetUtil.UTF_8);
		response.setHeader("Content-Disposition", StrUtil.format("attachment;filename={}", URLUtil.encode(fileName, charset)));
		response.setContentType(contentType);
		
		// 导出到excel文件流
		ServletOutputStream outs = null;
		try {
			outs = response.getOutputStream();
			excelWriter.flush(outs, false);
		} catch (IOException e) {
			throw new UtilException(e);
		} finally {
			IoUtil.close(outs);
		}
        
    }
    
    /**
     * 导出当前excel内容到输出流。
     * @param outstream
     * @return
     */
    public void flushToOutputStream(OutputStream outstream) {
        // 导出到excel文件流
        excelWriter.flush(outstream, false);
    }


    /**
     * 导入数据模型列表到浏览器端。返回表格总行数，总列数。
     * @param voList
     * @param fileName
     * @param response
     * @return
     */
    public <D> Point doExport(Class<D> modelClazz, List<D> voList, String fileName, HttpServletResponse response) {
        Point result = this.writeModels(0, 0, createAnnotationModelWriter(modelClazz), voList, true);
        // 导出到excel文件流
        this.flushToHttpResponse(fileName, response);

        return result;
    }
    

    /**
     * 导入数据模型列表到输出流端。返回表格总行数，总列数。
     * @param voList
     * @param fileName
     * @param response
     * @return
     */
    public <D> Point doExport(Class<D> modelClazz, List<D> voList, OutputStream outstream) {
        Point result = this.writeModels(0, 0, createAnnotationModelWriter(modelClazz), voList, true);
        
        // 导出到excel文件流
        excelWriter.flush(outstream, false);
        
        return result;
    }
    
    public CellWriter getCellWriter() {
        return cellWriter;
    }
    
    private class DefaultCellWriter implements CellWriter{
        private ExcelWriter excelWriter0;

        public DefaultCellWriter(ExcelWriter excelWriter) {
            super();
            this.excelWriter0 = excelWriter;
        }

        @Override
        public void writeCellValue(int row, int col, Object val) {
            this.writeCellValue(row, col, val, null, null, false);            
        }

        @Override
        public void writeCellValue(int row, int col, Object val, CellStyleCustomizer cellStyleCustomizer) {
            this.writeCellValue(row, col, val, null, cellStyleCustomizer, false);     
        }

        @Override
        public void writeCellValue(int row, int col, Object val, Point mergeOption) {
            this.writeCellValue(row, col, val, mergeOption, null, false);     
        }

        @Override
        public void writeCellValue(int row, int col, Object val, Point mergeOption, CellStyleCustomizer cellStyleCustomizer, boolean removeMergeRegion) {
            // 样式
            XSSFCellStyle style = createBasicStyle();
            if(cellStyleCustomizer != null) {
                style = cellStyleCustomizer.doCustomize(style);
            }
            
            
            if(mergeOption == null || mergeOption.x <= 1 && mergeOption.y <= 1) {
                excelWriter0.writeCellValue(col, row, val);
            }else {
            	if(removeMergeRegion) {
            		removeMergeRegion(row, col);
            	}
                excelWriter0.merge(row, row + mergeOption.y - 1, col, col + mergeOption.x - 1, val, false);
            }

            // 样式
            if(style != null) {
                excelWriter0.setStyle(style, col, row);
            }
            
        }
        
        
        /**
         * 若指定坐标为合并单元格区域，则解除合并。返回并解除的区域信息。
         * @param row
         * @param col
         * @return
         */
        private CellRangeAddress removeMergeRegion(int row, int col) {
        	Sheet sheet = excelWriter0.getSheet();
        	final int sheetMergeCount = sheet.getNumMergedRegions();
        	CellRangeAddress result = null;
        	int caIndex = -1;
        	for (int i = 0; i < sheetMergeCount; i++) {
        		CellRangeAddress ca = sheet.getMergedRegion(i);
        		if (row >= ca.getFirstRow() && row <= ca.getLastRow()
        				&& col >= ca.getFirstColumn() && col <= ca.getLastColumn()) {
        			caIndex = i;
        			result = ca;
        			break;
        		}
        	}

        	if(caIndex >= 0) {
        		sheet.removeMergedRegion(caIndex);
        	}

        	return result;
        }

		@Override
        public XSSFCellStyle createBasicStyle() {
            CellStyle style = excelWriter0.createCellStyle();
            style.setAlignment(HorizontalAlignment.CENTER);
            style.setVerticalAlignment(VerticalAlignment.CENTER);

            style.setBorderBottom(BorderStyle.THIN);
            style.setBorderLeft(BorderStyle.THIN);
            style.setBorderRight(BorderStyle.THIN);
            style.setBorderTop(BorderStyle.THIN);
            
            style.setFont(excelWriter0.createFont());

            return (XSSFCellStyle) style;
        }

        @Override
        public void setColumnWidth(int col, int width) {
            excelWriter0.setColumnWidth(col, width);            
        }
    }
    

    public XSSFCellStyle getHeaderCellStyle() {
        return headerCellStyle;
    }
    
   

}
